//////////////////////////////////////////////
// LinearPager.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Includes ---------------------------------

// nkMemory
#include "../../Containers/String.h"

#include "../../Log/LogManager.h"

#include "../../Pointers/UniquePtr.h"

#include "../MemorySpaces/DefaultMemorySpace.h"
#include "../MemorySpaces/DefaultMemorySpaceAllocator.h"

// Standards
#include <deque>

/// Class ------------------------------------

namespace nkMemory
{
	// T = return type des allocations
	// U = memory space à utiliser
	template <typename T = char*, typename U = DefaultMemorySpace>
	class LinearPager final
	{
		public :

			// Constructeur
			LinearPager (unsigned long long pageSize, UniquePtr<MemorySpaceAllocator<U>> allocator = makeUnique<DefaultMemorySpaceAllocator<U>>()) noexcept
			:	_spaceAllocator (std::move(allocator)),
				_pages (),
				_pageSize (pageSize),
				_currentOffset (0)
			{
				// First page always available to ease management
				_allocatePage() ;
			}

			LinearPager (const LinearPager&) noexcept = delete ;

			LinearPager (LinearPager&&) noexcept = default ;

		public :

			// Getters
			unsigned long long getPageSize () const
			{
				return _pageSize ;
			}

			unsigned long long getCurrentOffset () const
			{
				return _currentOffset ;
			}

			unsigned long long getAllocatedPageCount () const
			{
				return _pages.size() ;
			}

			unsigned long long getAvailablePageCount () const
			{
				return _availablePages.size() ;
			}

		public :

			// Allocations
			// Obtention zone mémoire
			T allocate (unsigned long long size, unsigned long long alignment = 1)
			{
				// Check de taille
				if (size > _pageSize || alignment > _pageSize)
				{
					String message ;
					message += "Attempted to allocate a chunk of memory of size " ;
					message += std::to_string(size) ;
					message += " and alignment of " ;
					message += std::to_string(alignment) ;
					message += " on a Pager having pages of size " ;
					message += std::to_string(_pageSize) ;
					message += ". Request cannot be handled, returning nullptr." ;

					LogManager::getInstance()->log(message, "LinearPager") ;

					return T() ;
				}

				// On calcule l'adresse à prendre
				unsigned long long toAlignOffset = _currentOffset % alignment ;
				unsigned long long alignedOffset = toAlignOffset ? _currentOffset + (alignment - toAlignOffset) : _currentOffset ;

				// Check si on a la place
				unsigned long long remainingSpace = getCurrentPageFreeMemory() ;

				if (remainingSpace < alignedOffset + size)
				{
					// Il nous faut une nouvelle page
					_availablePages.pop_front() ;

					if (_availablePages.empty())
						_allocatePage() ;

					// Reset des offsets
					_currentOffset = 0 ;
					alignedOffset = 0 ;
				}

				// Et on demande l'allocation
				T allocation = _availablePages.front()->getOffsetPtr(alignedOffset) ;
				_currentOffset = alignedOffset + size ;

				return allocation ;
			}

			// Reset de mémoire (état pages, ne libère aucune mémoire)
			void reset ()
			{
				// On reset notre état pour réutiliser les pages disponibles
				_availablePages.clear() ;

				for (const UniquePtr<U>& entry : _pages)
					_availablePages.emplace_back(entry.get()) ;

				_currentOffset = 0 ;
			}

		public :

			// Utilitaires
			unsigned long long getCurrentPageFreeMemory () const
			{
				return _pageSize - _currentOffset ;
			}

		public :

			// Operators
			LinearPager& operator= (const LinearPager&) noexcept = delete ;
			
			LinearPager& operator= (LinearPager&&) noexcept = default ;

		private :

			// Functions
			// Allocation de page
			void _allocatePage ()
			{
				_pages.emplace_back(_spaceAllocator->allocate(_pageSize)) ;
				_availablePages.emplace_back(_pages.back().get()) ;
			}

		private :
			
			// Attributes
			// Allocator used
			UniquePtr<MemorySpaceAllocator<U>> _spaceAllocator ;

			// Active pages
			std::deque<UniquePtr<U>> _pages ;
			std::deque<U*> _availablePages ;

			// State
			unsigned long long _pageSize ;
			unsigned long long _currentOffset ;
	} ;
}